﻿using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;
using ModbusTCP;

namespace FRS.Devices
{
    public class CECC : DeviceBase
    {
        private const int PORT = 502;
        private const int OUTPUTS = 8;
        private readonly Queue commandQueue;
        Master busclient;

        public CECC(Device device)
            : this(device.Address, device.Name)
        {
        }

        public CECC(string address, string name)
            : base(address, name)
        {
            commandQueue = Queue.Synchronized(new Queue());
            

            /* default output names */
            for (int i = 0; i < OUTPUTS; i++)
            {
                string output = "output " + i;
                OutputNames.Add(output);
                
                /* create the output treenodes */
                OutputTreeNode outputNode = new OutputTreeNode(this) { Text = output, OutputNr = i };
                RootNode.Nodes.Add(outputNode);
                OutputNodes.Add(outputNode);
                
                /*create the output menuitems*/
                OutputToolStripMenuItem outputMenuItem = new OutputToolStripMenuItem(i) { Text = output, CheckOnClick = true };
                outputMenuItem.Click += outputMenuItem_Click;
                RootMenuItem.DropDownItems.Add(outputMenuItem);
                OutputMenuItems.Add(outputMenuItem);
            }

            ToolStripSeparator tss = new ToolStripSeparator();
            RootMenuItem.DropDownItems.Add(tss);

            ToolStripMenuItem tsmi_on = new ToolStripMenuItem("all on");
            tsmi_on.Click += tsmi_on_Click;
            RootMenuItem.DropDownItems.Add(tsmi_on);

            ToolStripMenuItem tsmi_off = new ToolStripMenuItem("all off");
            tsmi_off.Click += tsmi_off_Click;
            RootMenuItem.DropDownItems.Add(tsmi_off);

            try
            {
                busclient = new Master(Address, (ushort)PORT, true);
            }
            catch (Exception)
            {
                IsAlive = false;
                busclient = null;
                AliveStateChanged();
            }
        }

        void tsmi_on_Click(object sender, EventArgs e)
        {
            SetOutputs(0xFF, 0xFF);
        }

        void tsmi_off_Click(object sender, EventArgs e)
        {
            SetOutputs(0xFF, 0x00);
        }
        
        void outputMenuItem_Click(object sender, EventArgs e)
        {
            OutputToolStripMenuItem output = sender as OutputToolStripMenuItem;
            if (output != null) SetOutput(output.OutputNr, output.Checked ? 1 : 0);
        }

        protected override void Update()
        {
            try
            {
                if (UpdateNames)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        /* commands in queue have a higher priority */
                        if (commandQueue.Count > 0) break;

                        string name = Encoding.UTF8.GetString(TcpSendreceive(new byte[] {4, (byte) j}, PORT)).TrimEnd('\0');

                        OutputNames[j] = name;
                    }
                }

                if (UpdateValues) 
                    OutputValues = TcpSendreceive(new byte[] {2}, PORT)[1];

                /* set commands are higher prior than get values and get names, so dequeue them all at once */
                while (commandQueue.Count > 0)
                    TcpSendreceive(commandQueue.Dequeue() as Byte[], PORT);
            }
            catch (Exception)
            {
                IsAlive = false;
                busclient = null;
                AliveStateChanged();
            }

            base.Update();
        }

        protected override byte[] TcpSendreceive(byte[] bytes, int port)
        {
            byte[] result = new byte[32];
            byte[] retVal = new byte[32];
            int startAddress = 2;
            switch (bytes[0])
            {
                // Write output
                case 1:
                    busclient.WriteMultipleRegister(1, 0, 0, new byte[] { 0, bytes[2], 0, bytes[1] }, ref retVal);
                    break;
                // Read outputs
                case 2:
                    busclient.ReadInputRegister(1, 0, 0, 2, ref retVal);
                    break;
                // Read name
                case 4:
                    busclient.ReadInputRegister(1, 0, (ushort)(startAddress + 16 * bytes[1]), 16, ref result);
                    for (int i = 0; i < 16;i++ )
                    {
                        retVal[i*2] = result[i*2 + 1];
                        retVal[i*2 + 1] = result[i*2];
                    }
                    break;
                // Write Name
                case 6:
                    byte[] name = new byte[32];
                    for (int i = 0; i < 16; i++)
                    {
                        name[i * 2] = bytes[i * 2 + 3];
                        name[i * 2 + 1] = bytes[i * 2 + 2];
                    }
                    //Buffer.BlockCopy(bytes, 2, name, 0, name.Length);
                    busclient.WriteMultipleRegister(1, 0, (ushort)(startAddress + bytes[1] * 16), name, ref result); 
                    break;
                default:
                    throw new Exception("Wrong Command");
            }

            IsAlive = true;
            AliveStateChanged();

            return retVal;
        }
        
        public override void SetOutputName(int outputNr, string outputName)
        {
            if (outputName == null) return;

            Byte[] bytes = new Byte[2 + 32];

            bytes[0] = 6;
            bytes[1] = (byte) outputNr;

            UTF8Encoding enc = new UTF8Encoding();
            Buffer.BlockCopy(enc.GetBytes(outputName), 0, bytes, 2, outputName.Length);

            commandQueue.Enqueue(bytes);

            base.SetOutputName(outputNr, outputName);
        }

        public override void SetOutput(int outputNr, int value)
        {
            commandQueue.Enqueue(new Byte[] { 1, (byte)(int)Math.Pow(2, outputNr), (byte)(Math.Pow(2, outputNr) * value) });

            base.SetOutput(outputNr, value);
        }

        public override void SetOutputs(int mask, int value)
        {
            commandQueue.Enqueue(new Byte[] {1, (byte) mask, (byte) value});

            base.SetOutputs(mask, value);
        }
    }
}
